
// This file contains code specific to the JavaScript back end.


(function(){


// Namespace.

var rio = this.rio;
if (!rio) {
  rio = this.rio = {};
}


// Imports.

var each = rio.each;
var eachField = rio.eachField;
var load = rio.load;
var log = rio.log; // rio.noop;


// Exports.

rio.compile = compile;
rio.run = run;


// Function definitions.

// TODO Some of this should be in rio-common.
// TODO Take text, etc., too.
// TODO What do we call the function that creates an environment with
// TODO instantiable functions (based on remaining environment), etc.?
// Returns a function to be executed.
function compile(script, handler) {

  // Pre-request the contents of each lib, so they can be loading while we compile.
  rio.listLibs(script);

  // See if we need any pre-work.
  // TODO Could make each stage auto-complete prior stages as needed.
  // TODO That would simplify logic here and make things more flexible.
  if (script.run) {
    handler();
  } else if (script.tree) {
    compileIt();
  } else if (script.text) {
    parseIt();
  } else {
    load(script.uri, function(text) {
      script.text = text;
      parseIt();
    });
  }

  function compileIt() {
    // Below is the only js-specific part, really.
    var js = generate(script);
    // log(js);
    with (script.env.defs) {
      script.run = eval(js);
    }
    if (script.exports) {
      var defs = script.run();
      eachField(script.exports, function(export) {
        // TODO Overloading. Use the def name, not the export name!
        export.defs[export.name] = defs[export.name];
      });
    }
    handler();
  }

  function parseIt() {
    rio.parse(script);
    rio.extract(script);
    rio.ensureLibs(script, function() {
      // TODO Right here we need to allow circular resolution, before compiling!
      eachField(script.imports, function($import, name) {
        // TODO Overloading!!!
        script.env.defs[name] = $import.defs[name];
        // TODO Metadata supposed to be stored here.
        // TODO Combine context inot env sometime.
        script.tree.context[name] = {name: name, type: rio.Def};
      });
      rio.resolve(script);
      // log(script.tree);
      if (script.errors.length) {
        handler();
      } else {
        // Only try to compile or run when we have no errors.
        // TODO Combine env and context once features are clear?
        implementCoreContext(script.tree.context);
        compileIt();
      }
    });
  }

}

// Generates JS source from the Rio AST.
// Assumes a valid tree. Don't let errors get this far.
function generate(script) {

  var js = ['(function() {\n'];

  // TODO No exports if any top-levels other than defs/structs/roles!!!
  // TODO Determine such presence at extract phase!!!

  if (script.exports) {
    push('return {\n');
    var first = true;
    eachField(script.exports, function(export, name) {
      if (first) {
        first = false;
      } else {
        push(',\n');
      }
      // TODO Address overloading (name mangling or other?).
      // TODO Name mangling (i.e., $blah) for JS keywords?
      push(name + ': ' + name);
    });
    push('\n};\n');
  }

  genExpr(script.tree);

  push('})');
  var result = js.join('');
  // log(result);
  return result;

  function genArgs(node) {
    // TODO Is this function in use? Should it be?
    each(node.expr, function(kid, k) {
      // TODO Unify with genBlock?
      if (k > 1) push(', ');
      genExpr(kid);
    });
  }

  function genBlock(node) {
    each(node.expr, function(kid) {
      // TODO We need to know our JS context, too.
      // TODO That impacts whether ',' or ';' (or other?), separates exprs.
      genExpr(kid);
      push(';\n');
    });
  }

  function genCall(node) {
    // TODO This needs some refinement.
    var gen =
      node.def.gen ||
      (node.def.gen = node.def.type === rio.Def ? generateStandardCall : generateGet);
    gen(node, push, genExpr);
  }

  function genDef(node) {
    // TODO Name mangling for multidefs and overrides.
    push('function ');
    push(node.id[0].text); // id
    push('(');
    each(node.params[0].expr, function(param, p) {
      if (p > 1) {
        push(', ');
      }
      push(param.id[0].text);
    });
    push(') {\n');
    genExpr(node.expr[0]); // block
    push('}');
  }

  function genExpr(node) {
    var type = node.type;
    switch (type) {
      // TODO Extract the constants to outer locals for performance?
      case rio.Block:
        genBlock(node);
        break;
      case rio.Call:
        genCall(node);
        break;
      case rio.Def:
        // TODO Generate as peers to the main function.
        // TODO That would allow export to other (even JS) code.
        genDef(node);
        break;
      case rio.Group:
        genGroup(node);
        break;
      case rio.Num:
        genNum(node);
        break;
      case rio.Str:
        genStr(node);
        break;
      case rio.Var:
        genVar(node);
        break;
    }
  }

  function genGroup(node) {
    // TODO Figure out the whole array plan!
    if (node.expr.length == 1) {
      push('(');
      genExpr(node.expr[0]);
      push(')');
    }
  }

  function genNum(node) {
    // TODO Will all our number literals always stay compatible with JS?
    push(node.text);
  }

  function genStr(node) {
    var text = node.text;
    // TODO Escape string contents.
    push('"' + text + '"');
  }

  function genVar(node) {
    // TODO Don't generate if it's already a param of the closest function!
    push('var ');
    push(node.id[0].text);
    if (node.expr) {
      push(' = ');
      genExpr(node.expr[0]);
    }
  }

  function push(text) {
    js.push(text);
  }

}

function generateGet(node, push, gen) {
  // The text of the id.
  push(node.id[0].text);
}

function generateStandardCall(node, push, gen) {
  // Second kid is the Id, and the second member is the text.
  push(node.id[0].text + '(');
  // TODO Determine by definition if parens are required.
  // TODO Support multis.
  each(node.args[0].expr, function(kid, k) {
    // TODO Call genArgs?
    // TODO Unify with genBlock?
    if (k > 0) push(', ');
    gen(kid);
  });
  push(')');
}

// TODO Provide default JS context implementation.
function implementCoreContext(context) {

  // TODO Some of these implementations may need to differ depending on the arg types (multidispatch and all).
  var gens = {
    add: generateBinaryOps('+'),
    and: generateBinaryOps('&&'),
    Boolean: [],
    equals: generateBinaryOps('=='),
    'false': generateGet,
    'if': $if,
    log: generateStandardCall,
    multiply: generateBinaryOps('*'),
    negate: generatePrefixOp('-'),
    not: generatePrefixOp('!'),
    or: generateBinaryOps('||'),
    'return': $return,
    String: [],
    'true': generateGet,
    Type: []
  };

  for (var g in gens) {
    context[g].gen = gens[g];
  }

  // Function definitions.

  function generateBinaryOps(op) {
    return function(node, push, gen) {
      // TODO Figure out when parens are and aren't needed?
      push('(');
      // Assume at least one arg, to have been checked at resolve.
      each(node.args[0].expr, function(kid, k) {
        if (k > 0) push(' ' + op + ' ');
        gen(kid);
      });
      push(')');
    }
  }

  function generatePrefixOp(op) {
    return function(node, push, gen) {
      // TODO Figure out when parens are and aren't needed?
      push('(');
      push(op);
      gen(node.args[0].expr[0]);
      push(')');
    }
  }

  function genStatements(block, push, gen) {
    each(block.expr, function(kid) {
      gen(kid);
      push(';\n');
    });
  }

  function $if(node, push, gen) {
    push(node.id[0].text + ' (');
    var args = node.args[0];
    gen(args.expr[0]);
    push(') {\n');
    if (args.expr.length > 1) {
      genStatements(args.expr[1], push, gen);
    }
    // TODO addition (else) ifs
    if (node.id.length > 1) {
      push('} else {\n');
      genStatements(node.args[1].expr[0], push, gen);
    }
    push('}');
  }

  function $return(node, push, gen) {
    push('return');
    var args = node.args[0];
    if (args.expr.length > 0) {
      push(' ');
      gen(args.expr[0]);
    }
  }

}

// TODO Put this into common! JS is just a plugin to it!
// TODO Put handler in the env, too?
function run(script, handler) {
  compile(script, function() {
    if (script.run) {
      // TODO Do we really want to execute as method on script? Maybe?
      script.run();
    }
    handler();
  });
}


})();
